package neuralNetwork.builder;

import java.util.List;
import java.util.Random;

import neuralModel.activationFunction.IActivationFunction;
import neuralModel.activationFunction.LinearActivationFunction;
import neuralModel.activationFunction.RadialActivationFunction;
import neuralModel.neuron.ClampedInputSensor;
import neuralModel.neuron.INeuron;
import neuralModel.neuron.ISignal;
import neuralModel.neuron.Neuron;
import neuralModel.neuron.RadialNeuron;
import neuralNetwork.INeuralNetwork;
import neuralNetwork.NeuralNetwork;
import neuralNetwork.NeuralNetworkUtils;

public class RadialNetworkBuilder
{

	public RadialNetworkBuilder()
	{

	}

	public INeuralNetwork buildNetwork(int inputNodeCount, int outputCount, List<double[]> centers)
	{
		RadialActivationFunction radialFunction = buildRadialActivationFunction(centers);

		INeuralNetwork neuralNetwork = new NeuralNetwork(2);

		buildInputLayer(neuralNetwork, inputNodeCount);

		// buildInnerLayer(neuralNetwork, 1, radialNodeCount, radialFunction,
		// centers);

		buildOutputLayer(neuralNetwork, outputCount, radialFunction, centers);

		NeuralNetworkUtils.FullyConnectNetworkLayers(neuralNetwork, .5);

		// addBiasToOutputs(neuralNetwork, .5);

		randomizeWeights(neuralNetwork, 0.5, 0.01, 7);

		return neuralNetwork;
	}

	public RadialActivationFunction buildRadialActivationFunction(List<double[]> centers)
	{
		double maxDistance = 0;
		double sum;
		double distance = 0;
		double[] differnece = new double[centers.get(0).length];
		for (double[] center1 : centers)
		{
			for (double[] center2 : centers)
			{
				if (center1 == center2)
					continue;

				for (int i = 0; i < center1.length; i++)
				{
					differnece[i] = center1[i] - center2[i];
				}

				sum = 0;
				for (int i = 0; i < differnece.length; i++)
				{
					sum += (differnece[i] * differnece[i]);
				}

				distance = Math.sqrt(sum);

				if (distance > maxDistance)
					maxDistance = distance;

			}
		}

		return new RadialActivationFunction(centers.size(), maxDistance);
	}

	public void buildInputLayer(INeuralNetwork neuralNetwork, int nodeCount)
	{
		String nodeHeader = "input_";
		INeuron neuron;
		for (int i = 0; i < nodeCount; i++)
		{
			neuron = new ClampedInputSensor(nodeHeader + i);
			neuralNetwork.addNeuron(neuron, 0);
		}
	}

	public void buildOutputLayer(INeuralNetwork neuralNetwork, int nodeCount,
	    RadialActivationFunction activationFunction, List<double[]> centers)
	{
		int outputLayerIndex = neuralNetwork.getLayerCount() - 1;
		String nodeHeader = "output_";
		INeuron neuron;
		for (int i = 0; i < nodeCount; i++)
		{
			neuron = new RadialNeuron(nodeHeader + i, activationFunction, centers.get(i));
			neuralNetwork.addNeuron(neuron, outputLayerIndex);
		}
	}

	public void buildInnerLayer(INeuralNetwork neuralNetwork, int layerIndex, int nodeCount,
	    RadialActivationFunction activationFunction, List<double[]> centers)
	{
		String nodeHeader = String.format("node_%d_", layerIndex);
		INeuron neuron;
		for (int i = 0; i < nodeCount; i++)
		{
			neuron = new RadialNeuron(nodeHeader + i, activationFunction, centers.get(i));
			neuralNetwork.addNeuron(neuron, layerIndex);
		}
	}

	public void addBiasToOutputs(INeuralNetwork neuralNetwork, double initialWeight)
	{
		INeuron biasNeuron = new ClampedInputSensor("bias");
		neuralNetwork.addNeuron(biasNeuron, 0);
		biasNeuron.setOutputValue(1.0);

		int outputLayerIndex = neuralNetwork.getLayerCount() - 1;

		for (INeuron neuron : neuralNetwork.getLayer(outputLayerIndex))
		{
			if (neuron.getNetworkLayer() > 0)
				neuralNetwork.connectNeurons(biasNeuron, neuron, initialWeight);
		}
	}

	public void randomizeWeights(INeuralNetwork neuralNetwork, double startWeight, double deltaMax, long seed)
	{
		Random rand = new Random(seed);
		double delta;
		for (ISignal signal : neuralNetwork.getSignalList())
		{
			delta = (rand.nextDouble() * deltaMax);
			if (rand.nextInt(2) == 0)
			{
				signal.setWeight(startWeight + delta);
			}
			else
			{
				signal.setWeight(startWeight - delta);
			}

		}
	}
}
